#include "../inc/snake.h"

/*
思路 :
    蛇身设计为一个一个节点

    进入贪吃蛇游戏
        1.显示贪吃蛇游戏界面
        2.显示贪吃蛇地图
            2.1显示历史最高分
                    showFont->display(text, 32, 100, 32, ShowFont::purple, ShowFont::white, 630, 154);
        3.显示贪吃蛇和游戏状态
            3.1蛇头跟蛇身图片大小15*15
            3.2蛇头图片
                3.2.1向上图片：./snake/upkun.bmp
                3.2.2向下图片：./snake/downkun.bmp
                3.2.3向左图片：./snake/leftkun.bmp
                3.2.4向右图片：./snake/rightkun.bmp
            3.3蛇身图片：./snake/body.bmp
            3.4食物图片：./snake/kun.bmp
        4.触摸屏控制贪吃蛇移动
            上方向键 位置：x:580,650 y:260,320
            下方向键 位置：x:580,650 y:400,480
            左方向键 位置：x:490,575 y:320,390
            右方向键 位置：x:665,735 y:320,390
            加速按键 位置：x:715,800 y:220,310
            暂停按键 位置：x:740,800 y:400,480
            4.1吃到自己或撞墙
                4.1.1：在左中心位置打印游戏结束图片:x=0,y=110
                4.1.2：判断本局是否是历史最高分：如果是，把最终分数存入文件./snake/score.txt
                4.1.3：通过触摸事件选择是否重新开始还是退出游戏：按钮大小：x:110，y:50
                    4.1.3.1从新开始位置：x=60,170,y=270,320
                    4.1.3.2退出游戏位置：x=300,410,y=270,320
            4.2吃到食物
                4.2.1：蛇身变长
                4.2.2：分数增加并打印分数：154，100, 40, 0, 0
                4.2.3：重新生成食物
        5.触摸屏点击返回游戏选择界面
*/ 

//声明ReturnInterfaceFlag类，不初始化
ReturnInterfaceFlag flag;
// 创建滑动检测器实例，获取触摸事件坐标
SwipeDetector Sdetector;

//创建贪吃蛇BMP对象,蛇名叫kun
BMP snakeInterface("./snake/snakeInterface.bmp");
BMP snakeMap("./snake/snakeMap.bmp");
BMP snakeMapEmptyChess("./snake/snakeMapEmptyChess.bmp");
BMP kun("./snake/kun.bmp");
BMP downkun("./snake/downkun.bmp");
BMP upkun("./snake/upkun.bmp");
BMP leftkun("./snake/leftkun.bmp");
BMP rightkun("./snake/rightkun.bmp");
BMP body("./snake/body.bmp");
BMP gameOver("./snake/gameOver.bmp");



//创建ShowFont对象,打印文字
ShowFont* showFont = ShowFont::instance();
char g_text[50]; // 用于存储打印文字
//停止线程标志位
bool stopThread = false;

/**
 * @brief SnakeGame 构造函数，初始化游戏状态
 *
 * @param boardWidth 游戏板宽度
 * @param boardHeight 游戏板高度
 * @param startX 蛇头初始X坐标
 * @param startY 蛇头初始Y坐标
 *
 * 初始化蛇的位置、方向、速度、分数等游戏状态
 */
SnakeGame::SnakeGame(int boardWidth, int boardHeight, int startX, int startY)
    : snakeHead_(nullptr), currentDirection_(Direction::None), inputDirection_(Direction::Right),
      snakeSpeed_(1000), foodScore_(5), snakeGameScore_(0), snakeLength_(1),
      isAlive_(true), food_(nullptr), boardWidth_(boardWidth), boardHeight_(boardHeight)
{ 
    initSnake(startX, startY);
}

/**
 * @brief 初始化蛇身
 *
 * 创建蛇头节点并初始化蛇身状态，包括：
 * 1. 设置蛇头节点的前后指针指向自身
 * 2. 将蛇头加入蛇身节点数组
 * 3. 设置初始蛇长为1
 * 4. 标记蛇为存活状态
 * 5. 生成初始食物
 * 6. 根据当前方向添加蛇身节点（当snakeLength_>1时）
 *
 * @param startX 蛇头初始X坐标
 * @param startY 蛇头初始Y坐标
 */
void SnakeGame::initSnake(int startX, int startY)
{
    snakeHead_ = std::make_shared<SnakeNode>(startX, startY); // 创建蛇头节点
    snakeHead_->setNext(snakeHead_);// 设置蛇头的下一个节点为自己
    snakeHead_->setPrev(snakeHead_);// 设置蛇头的上一个节点为自己
    bodyNodes_.push_back(snakeHead_);// 将蛇头加入蛇身节点数组
    snakeLength_ = 1; // 初始长度为1
    isAlive_ = true;// 蛇存活
    food_ = createFood();// 初始化食物

    // 如果 snakeLength_ > 1，则添加蛇身节点
    for (int i = 1; i < snakeLength_; ++i) 
    {
        // 创建新的头部节点
        auto newHead = std::make_shared<SnakeNode>(
            snakeHead_->getX() + (currentDirection_ == Direction::Left ? -1 : (currentDirection_ == Direction::Right ? 1 : 0)),
            snakeHead_->getY() + (currentDirection_ == Direction::Up ? -1 : (currentDirection_ == Direction::Down ? 1 : 0))
        );

        // 将新头部插入到链表头部
        newHead->setNext(snakeHead_);
        newHead->setPrev(snakeHead_->getPrev());
        snakeHead_->getPrev()->setNext(newHead);
        snakeHead_->setPrev(newHead);

        // 更新蛇头指针
        auto oldHead = snakeHead_;
        snakeHead_ = newHead;
    }
}

/**
 * @brief 销毁贪吃蛇链表
 *
 * 遍历并清理蛇身节点，使用shared_ptr自动管理内存释放。
 * 注意：无需手动删除节点，智能指针会自动处理内存回收。
 */
void SnakeGame::destroySnake()
{
    if (!snakeHead_)
        return;

    auto current = snakeHead_->getNext();
    while (current != snakeHead_) 
    {
        auto temp = current;
        current = current->getNext();
        // 不需要手动删除，shared_ptr会自动管理
    }
    // 不需要手动删除snakeHead_，shared_ptr会自动管理
}

/**
 * 重置游戏状态
 *
 * 销毁当前蛇并重新初始化，重置分数、蛇长度和存活状态，
 * 同时生成新的食物。用于游戏重新开始时的状态重置。
 */
void SnakeGame::restart()
{
    destroySnake();    // 销毁当前蛇
    initSnake(15, 15); // 假设起始位置为(15,15)
    snakeGameScore_ = 0;
    snakeLength_ = 1;
    isAlive_ = true;
    food_ = createFood();
    // 这里可以添加重新开始的其他初始化逻辑
}

/**
 * 设置蛇的移动方向
 *
 * @param dir 新的移动方向
 * @note 该方法会阻止蛇直接反向移动（例如当前向上移动时不允许直接改为向下移动）
 */
void SnakeGame::setDirection(Direction dir)
{
    // 防止直接反向移动
    if ((currentDirection_ == Direction::Up && dir == Direction::Down) ||
        (currentDirection_ == Direction::Down && dir == Direction::Up) ||
        (currentDirection_ == Direction::Left && dir == Direction::Right) ||
        (currentDirection_ == Direction::Right && dir == Direction::Left)) 
    {
        return;
    }
    inputDirection_ = dir;// 更新输入方向
}

/**
 * @brief 贪吃蛇游戏主循环
 *
 * 该函数实现了贪吃蛇游戏的核心逻辑循环，包括：
 * 1. 显示游戏界面、地图和分数
 * 2. 处理触摸屏输入控制蛇的移动方向
 * 3. 更新蛇的位置并检查碰撞
 * 4. 检测是否吃到食物
 * 5. 创建一个线程根据蛇头的方向自动向前移动
 * 6. 控制游戏速度
 * 
 * 游戏循环会持续运行直到游戏结束条件触发（碰撞或主动退出）。
 * 触摸屏控制区域包括四个方向键和一个返回按钮。
 */
int SnakeGame::play()
{
    // 显示贪吃蛇游戏界面
    snakeInterface.show(Lcd::instance()->addr(), 0, 0);

    // 判断是否是用户登录
    if (flag.userLoginFlag == true)
    {
        // 显示历史最高分
        sprintf(g_text, "%d", UserManager::getInstance().getUserHighScore(flag.UserName, "Snake"));
        showFont->display(g_text, 32, 100, 32, ShowFont::purple, ShowFont::white, 630, 154);
        // 显示用户名
        sprintf(g_text, "%s", flag.UserName.c_str());//flag.UserName为用户名
        showFont->display(g_text, 32, 100, 32, ShowFont::purple, ShowFont::white, 590, 15);
    }

    stopThread=true;//开启
    // 创建一个线程根据蛇头的方向自动向前移动
    std::thread moveThread(&SnakeGame::autoMoveSnake, this);
    moveThread.detach(); 

    TimeDisplay snake_timeDisplay;//创建时间类实例，用于显示时间
    snake_timeDisplay.start();//打印时间
    
    while (!isGameOver())//如果游戏没有结束 
    {
        snakeMap.show(Lcd::instance()->addr(), 0, 0);// 显示贪吃蛇游戏地图   
        printSnake();// 显示贪吃蛇和游戏状态

        //处理输入方向
        if (inputDirection_ != Direction::None) 
        {
            currentDirection_ = inputDirection_;
            inputDirection_ = Direction::None;
        }

        Sdetector.detectSwipe();// 检测滑动事件,获取触摸事件坐标
        std::cout << "x = " << Sdetector.getX() << " " << "y = " << Sdetector.getY() << std::endl;
        
        // 触摸屏控制贪吃蛇移动
        // 上方向键 位置：x:580,650 y:260,320
        if (Sdetector.getX() >= 565 && Sdetector.getX() <= 660 && Sdetector.getY() >= 255 && Sdetector.getY() <= 330)
        {
            // 设置贪吃蛇向上移动
            setDirection(Direction::Up);
            std::cout << "up" << std::endl;
        }
        // 下方向键 位置：x:580,650 y:400,480
        else if (Sdetector.getX() >= 565 && Sdetector.getX() <= 660 && Sdetector.getY() >= 400 && Sdetector.getY() <= 480)
        {
            // 设置贪吃蛇向下移动
            setDirection(Direction::Down);
            std::cout << "down" << std::endl;
        }
        // 左方向键 位置：x:490,575 y:320,390
        else if (Sdetector.getX() >= 480 && Sdetector.getX() <= 575 && Sdetector.getY() >= 330 && Sdetector.getY() <= 405)
        {
            // 设置贪吃蛇向左移动
            setDirection(Direction::Left);
            std::cout << "left" << std::endl;
        }
        // 右方向键 位置：665,735 y:320,390
        else if (Sdetector.getX() >= 650 && Sdetector.getX() <= 745 && Sdetector.getY() >= 330 && Sdetector.getY() <= 405)
        {
            // 设置贪吃蛇向右移动
            setDirection(Direction::Right);
            std::cout << "right" << std::endl;
        }
        // 触摸屏点击返回游戏选择界面
        else if (Sdetector.getX() >= 700 && Sdetector.getX() <= 800 && Sdetector.getY() >= 0 && Sdetector.getY() <= 80)
        {
            //游戏结束
            cout << "Game Over!" << endl;
            stopThread=false;//关闭线程

            snake_timeDisplay.stop();//停止时间显示

            // 延时1秒，先让自动移动的线程先退出
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
            return 1; //返回游戏选择界面
        }

        // 检查碰撞
        if (checkCollision()) 
        {
            // 延时0.5秒，先让自动移动的线程先退出
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
            return 0; // 游戏结束
        }
    }
}

/**
 * @brief 自动控制蛇的移动
 *
 * 该函数实现蛇的自动移动逻辑，包括：
 * 1. 提供3档速度调节（二倍速/三倍速/恢复默认）
 * 2. 游戏暂停/继续功能
 * 3. 碰撞检测和退出控制
 *
 * 通过坐标检测用户操作，控制移动速度和游戏状态。
 * 使用sleep_for控制蛇的移动间隔时间。
 */
void SnakeGame::autoMoveSnake() // 自动移动蛇
{
    // 创建一个按钮来控制蛇的移动速度
    int snakespeed = 400;
    int speed = 1;// 挡位
    bool isPaused = false; // 游戏是否暂停

    //创建音乐类，用于播放吃到食物时播放一次
    MusicPlayer snake_music;

    while (this->checkCollision() == false&&stopThread==true) // 如果蛇没有碰到边界，就继续移动
    {
        //设置三个挡位，按了前俩个加速按键就挨个加速,第三个回复原来的速度
        if (Sdetector.getX() >= 715 && Sdetector.getX() <= 800 && Sdetector.getY() >= 220 && Sdetector.getY() <= 310)
        {
            switch(speed)
            {
                case 1:
                    snakespeed = 400;
                    break;
                case 2:
                    snakespeed = 200;
                    break;
                case 3:
                    snakespeed = 800;
                    break;
            }
            speed++;
            if (speed > 3)
                speed = 1;
            std::cout << "speed = " << speed << std::endl;
            
            Sdetector.setXY(0, 0);// 把获取的坐标置零
        }
        //如果按了暂停键就暂不执行，再次按下恢复执行
        else if(Sdetector.getX() >= 735 && Sdetector.getX() <= 800 && Sdetector.getY() >= 400 && Sdetector.getY() <= 480)
        {
            isPaused = !isPaused; // 切换暂停状态
            std::cout << "Game is " << (isPaused ? "paused" : "resumed") << std::endl;
            
            Sdetector.setXY(0, 0);// 把获取的坐标置零
        }
        //如果没有暂停就执行移动
        else if (!isPaused)
        {
            this->moveSnake();//移动蛇
            snakeMap.show(Lcd::instance()->addr(), 0, 0);
            this->printSnake();//打印蛇
            // 延时，控制蛇的移动速度
            std::this_thread::sleep_for(std::chrono::milliseconds(snakespeed));
            
            //如果蛇碰到边界
            if (this->checkCollision())
            {
                endGame(currentDirection_ == Direction::Up || currentDirection_ == Direction::Down ? 1 : 0); // 简化处理
                // 显示游戏结束界面
                gameOver.show(Lcd::instance()->addr(), 0, 140);
            }

            // 检查是否吃到食物
            if (isNextFood())
            {
                snake_music.play("./snake/eat.mp3");
                food_ = createFood();
            }
        }
    }
}

/**
 * 移动蛇的节点位置
 *
 * 根据当前方向更新蛇头位置，并处理吃到食物或移动的逻辑：
 * 1. 创建新蛇头节点并更新链表关系
 * 2. 如果未吃到食物则移除尾部节点
 * 3. 吃到食物时增加蛇长度和游戏分数
 * 4. 更新分数显示
 *
 * @note 蛇死亡(isAlive_为false)时直接返回不执行移动
 */
void SnakeGame::moveSnake()
{
    if (!isAlive_)
        return;

    // 创建新的头部节点
    auto newHead = std::make_shared<SnakeNode>(
        snakeHead_->getX() + (currentDirection_ == Direction::Left ? -1 : (currentDirection_ == Direction::Right ? 1 : 0)),
        snakeHead_->getY() + (currentDirection_ == Direction::Up ? -1 : (currentDirection_ == Direction::Down ? 1 : 0))
    );

    // 将新头部插入到链表头部
    newHead->setNext(snakeHead_);
    newHead->setPrev(snakeHead_->getPrev());
    snakeHead_->getPrev()->setNext(newHead);
    snakeHead_->setPrev(newHead);

    // 更新蛇头指针
    auto oldHead = snakeHead_;
    snakeHead_ = newHead;

    // 如果没有吃到食物，移除尾部节点
    if (snakeHead_->getX() != food_->getX() || snakeHead_->getY() != food_->getY()) 
    {
        auto tail = snakeHead_->getPrev();
        tail->getPrev()->setNext(snakeHead_);
        snakeHead_->setPrev(tail->getPrev());

        // 从bodyNodes中移除尾部节点
        auto it = std::find_if(bodyNodes_.begin(), bodyNodes_.end(),
            [tail](const std::shared_ptr<SnakeNode>& node) { return node.get() == tail.get(); });
        if (it != bodyNodes_.end()) 
        {
            bodyNodes_.erase(it);
        }
        // 不需要手动删除tail，shared_ptr会自动管理
        snakeLength_--;
    }
    else
    {
        // 吃到食物，增加长度
        snakeLength_++;// 增加长度
        snakeGameScore_ += foodScore_;// 增加分数并打印分数
        // 把当前分数存入g_text，并通过文字打印出来打印：
        sprintf(g_text, "%d", snakeGameScore_);
        showFont->display(g_text, 32, 60, 32, ShowFont::purple, ShowFont::white, 630, 224);
    }
}

/**
 * 检查蛇头是否即将吃到食物
 * @return 如果蛇头位置与食物位置重合则返回true，否则返回false
 */
bool SnakeGame::isNextFood() const
{
    return (snakeHead_->getX() == food_->getX() && snakeHead_->getY() == food_->getY());
}

/**
 * @brief 处理蛇吃到食物的逻辑
 * @note 实际吃到食物的处理已在 moveSnake() 方法中实现
 */
void SnakeGame::eatFood()
{
    // 这里只是想说：因为是要处理链表的长度问题
    // 所以moveSnake中已经处理了吃到食物的情况
}

/**
 * 创建食物节点
 *
 * 随机生成一个不与蛇身重叠的坐标位置，并返回对应的食物节点
 *
 * @return std::shared_ptr<SnakeNode> 新创建的食物节点
 */
std::shared_ptr<SnakeNode> SnakeGame::createFood()
{
    // 创建随机数生成器
    static std::random_device rd;
    static std::mt19937 gen(rd());
    static std::uniform_int_distribution<int> x_dist(0, boardWidth_ - 1);
    static std::uniform_int_distribution<int> y_dist(0, boardHeight_ - 1);

    int x, y;// 随机坐标位置变量
    bool isRepeat;// 标记是否重复生成坐标的标志变量

    do 
    {
        isRepeat = false;
        x = x_dist(gen);// 生成随机X坐标位置
        y = y_dist(gen);// 生成随机Y坐标位置
        auto current = snakeHead_;// 当前蛇头节点指针
        do {
            if (current->getX() == x && current->getY() == y)// 检查是否与蛇身重叠 
            {
                isRepeat = true;
                break;
            }
            current = current->getNext();
        } while (current != snakeHead_);
    } while (isRepeat);

    return std::make_shared<SnakeNode>(x, y);
}

/**
 * 检查蛇是否发生碰撞
 *
 * @return true 如果蛇头撞到墙壁或自身，否则返回 false
 *
 * 碰撞检测分为两部分：
 * 1. 检查蛇头是否超出游戏边界（撞墙）
 * 2. 检查蛇头是否与蛇身其他部分重叠（撞到自己）
 */
bool SnakeGame::checkCollision() const
{
    // 检查是否撞墙
    if (snakeHead_->getX() < 0 || snakeHead_->getX() >= boardWidth_ || 
        snakeHead_->getY() < 0 || snakeHead_->getY() >= boardHeight_) 
    {
        return true;
    }

    // 检查是否撞到自己
    auto current = snakeHead_->getNext();
    while (current != snakeHead_) 
    {
        if (snakeHead_->getX() == current->getX() && snakeHead_->getY() == current->getY()) 
        {
            return true;
        }
        current = current->getNext();
    }

    return false;
}

/**
 * @brief 打印贪吃蛇游戏状态
 *
 * 该方法负责在屏幕上绘制当前游戏状态，包括：
 * 1. 蛇头（根据移动方向显示不同朝向）
 * 2. 食物位置
 * 3. 蛇身所有节点
 * 同时会在控制台输出各元素的坐标信息。
 *
 * @note 使用Lcd显示接口进行图形绘制，坐标会乘以15进行缩放
 */
void SnakeGame::printSnake() const
{
    // 根据移动方向打印蛇头
    std::cout << "Snake Head: (" << snakeHead_->getX() << ", " << snakeHead_->getY() << ")\n";
    if (currentDirection_ == Direction::Up)//上移动
    {
        upkun.show(Lcd::instance()->addr(), snakeHead_->getX() * 15, snakeHead_->getY() * 15);
    }
    else if (currentDirection_ == Direction::Down)//下移动
    {
        downkun.show(Lcd::instance()->addr(), snakeHead_->getX() * 15, snakeHead_->getY() * 15);
    }
    else if (currentDirection_ == Direction::Left)//左移动
    {
        leftkun.show(Lcd::instance()->addr(), snakeHead_->getX() * 15, snakeHead_->getY() * 15);
    }
    else if (currentDirection_ == Direction::Right)//右移动
    {
        rightkun.show(Lcd::instance()->addr(), snakeHead_->getX() * 15, snakeHead_->getY() * 15);
    }

    // 打印食物
    std::cout << "Food: (" << food_->getX() << ", " << food_->getY() << ")"<<std::endl;
    kun.show(Lcd::instance()->addr(), food_->getX() * 15, food_->getY() * 15);
    // 遍历打印蛇身
    auto current = snakeHead_->getNext();
    while (current != snakeHead_) // 遍历蛇身
    {
        std::cout << "Body: (" << current->getX() << ", " << current->getY() << ") ";// 打印蛇身坐标，根据蛇身坐标绘制蛇身
        body.show(Lcd::instance()->addr(), current->getX() * 15, current->getY() * 15);
        current = current->getNext();// 遍历下一个节点
    }
    std::cout << std::endl;
}

/**
 * @brief 结束游戏并处理游戏结束逻辑
 *
 * @param reason 游戏结束原因：0-撞墙，1-撞到自己
 *
 * 功能说明：
 * 1. 设置游戏状态为死亡
 * 2. 根据结束原因显示对应提示信息
 * 3. 显示游戏结束界面
 * 4. 如果当前分数高于历史最高分，将分数保存到./snake/score.txt文件中
 */
void SnakeGame::endGame(int reason)
{
    isAlive_ = false;
    if (reason == 0) 
    {
        std::cout << "Game Over! You hit the wall.\n";
    } else if (reason == 1) 
    {
        std::cout << "Game Over! You hit yourself.\n";
    }

    cout<<flag.UserName<<"的分数是："<<snakeGameScore_<<endl;

    // 判断是否是用户登录
    if (flag.userLoginFlag == true)
    {
        cout<<flag.UserName<<"的分数是："<<snakeGameScore_<<endl;
        // 判断本局是否是历史最高分:如果是，把最终分数存入文件
        if (snakeGameScore_ > UserManager::getInstance().getUserHighScore(flag.UserName, "Snake")) // 打开文件用于写入
        {
           UserManager::getInstance().updateUserScore(flag.UserName,"Snake",snakeGameScore_); // 更新分数
        }
    }
}

/**
 * 检查游戏是否结束
 * @return 如果蛇已死亡返回true，否则返回false
 */
bool SnakeGame::isGameOver() const
{
    return !isAlive_;
}

